import datetime
import os.path
import random
import secrets
import time
import logging
import pprint
import requests
from globally import LogFile, product_id, CVECSVFILE, INFLUENCE_INFO, CVEJSONFILE
import csv, json
from functools import cached_property

class KylinCVE:
    CVE_URL = "https://server.kylinos.cn:18089/api/cve_manager/own_cve_info"  # CVE搜索接口

    # 输入信息：1、CVE编号 2、产品id 3、cpu架构
    def __init__(self, cve_number, productid, framework):
        self._cve_number = cve_number
        self._productid  = productid
        self._framework = framework
        self._response_detail = self._get_cve_response          # 提前获取post请求结果，避免每次都重复调用

    # 获取cve网页返回信息，POST请求
    @property
    def _get_cve_response(self) -> dict:
        UserAgent_List = [
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.84 Safari/537.36 RuxitSynthetic/1.0 v100801619688 t564353316429258614 athccb4ad0b altpub cvcv=2 smf=0",
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 GLS/100.10.9989.100",
            "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4494.0 Safari/537.36",
            "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/0.4.154.18 Safari/525.19",
            "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.462.0 Safari/534.3"]
        headers = {
            "User-Agent": secrets.choice(UserAgent_List),
            "Host": "server.kylinos.cn:18089",
            "Connection": "keep-alive"}
        data = {"all_title": self._cve_number}
        post_error = 0
        response = {}
        while True:
            try:
                response = requests.post(url=KylinCVE.CVE_URL, data=data, headers=headers, verify=False).json()
                break
            except:
                if post_error >= 5:
                    response = {"detail": {"error": ""}}
                    response["detail"]["error"] = "-99"
                    break
                post_error = post_error + 1
                print(f"{self._cve_number}：请求失败{post_error}次")
        # pprint.pprint(response["detail"])
        return response["detail"]

    # 漏洞影响状态
    @property
    def _influence(self) -> int:
        if not self._response_detail :
            # 返回值没有，说明漏洞未被收录，返回值 -1
            return -1
        elif "error" in self._response_detail.keys() and self._response_detail["error"] == "-99":
            return -99
        else:
            # 根据POST请求返回的数据中product_component_list里的status字段，来决定影响状态
            # 2 —— 影响  4 —— 不影响  99 —— 没有该版本系统的数据   -99 —— 网络请求失败超过5次
            # 其中 2， 4 是由漏洞平台返回值提供。99由代码自定义
            component_list = self._response_detail["product_component_list"]
            influence_status = 99
            for mumber in component_list:
                if str(mumber["product_id"]) == self._productid and mumber["framework"] == self._framework:
                    if int(mumber["status"]) < int(influence_status):
                        influence_status = mumber["status"]
                    # pprint.pprint(mumber)
            return int(influence_status)

    # 漏洞详情
    @property
    def _detail(self) -> str:
        cve_detail = self._response_detail["cve_detail"]
        return cve_detail


    # 解决方案
    @property
    def _solution(self) -> list:
        product_compnent_list = self._response_detail["product_component_list"]
        solution = []
        for mumber in product_compnent_list:
            if str(mumber["product_id"]) == self._productid and mumber["framework"] == self._framework:
                if mumber["status"] != "4":
                    fix_pack = {"packname": mumber["component_name"],
                                "status": mumber["status"],
                                "version": mumber["version"]}
                    solution.append(fix_pack)
        return solution

    @property
    def report(self) -> dict:
        # cve报告的结构体
        Struct_cve_report = {
            "cve_number": self._cve_number,
            "productid": self._productid,
            "framework": self._framework,
            "influence": int(),  # influence:  -1 —— 表示漏洞未被收录  2 —— 影响  4 —— 不影响  99 —— 没有该版本系统的数据
            "detail": "",
            "solution": []
            # solution[fix_pack1, fix_pack2,...]
            # fix_pack = {"packname": "", "version": ""}
        }
        self.cve_report = Struct_cve_report
        self.cve_report["influence"] = self._influence
        if self.cve_report["influence"] > 0:  # 判断该CVE网站是否存在有效数据
            # 存在有效数据。
            self.cve_report["detail"] = self._detail
            self.cve_report["solution"] = self._solution
        return self.cve_report

class KylinCveProcessor:
    _CveResult_Csvfile = CVECSVFILE
    _Productid = product_id
    _CveResult_JsonFile = CVEJSONFILE

    def __init__(self, cvelist, productid, framework):
        self.cvelist = cvelist
        self.productid = productid
        self.framework = framework


    def _get_all_cve_reports(self) -> list:
        all_cve_reports = []
        count = 0
        for cve_number in self.cvelist:
            count = count + 1
            print("{}/{}  漏洞编号: {} ".format(count, len(self.cvelist), cve_number))
            cve_report = KylinCVE(cve_number, self.productid, self.framework).report
            all_cve_reports.append(cve_report)
            print("状态： {}，  解决方案：{}".format(INFLUENCE_INFO.get(str(cve_report["influence"]), "返回值异常"),["{}: {}".format(i["packname"], i["version"]) for i in cve_report["solution"]]))
            sleeptime = random.uniform(1,3)
            print(f"防反爬虫休眠{sleeptime}秒")
            time.sleep(sleeptime) # 单线程+延时，减小服务端压力
        return all_cve_reports

    # 漏洞处理完成后，对所有cve数据进行总结
    def _processor_summary(self, all_cve_reports) -> None:
        # CVE网页未收录
        def _cvelist_cvenotfound_output(all_cve_reports):
            count = 0
            namelist = []
            for cve_report in all_cve_reports:
                if cve_report["influence"] == -1:
                    count = count + 1
                    namelist.append(cve_report["cve_number"])
            print("CVE漏洞未收录： {}个，  列表如下 {}".format(count, "  ".join(namelist)))
        # CVE没有对应版本解决方案
        def _cvelist_solutionnotfound_output(all_cve_reports):
            count = 0
            namelist = []
            for cve_report in all_cve_reports:
                if cve_report["influence"] == 99:
                    count = count + 1
                    namelist.append(cve_report["cve_number"])
            print("CVE漏洞收录，但未有对应版本解决方案： {}个，  列表如下: {}".format(count, "  ".join(namelist)))
        # CVE需要升级包
        def _cvelist_needupdate_package_output(all_cve_reports):
            packlist = []
            for cve_report in all_cve_reports:
                if cve_report["influence"] == 2:
                    for mumber in cve_report["solution"]:
                        if mumber["packname"] not in packlist:
                            packlist.append(mumber["packname"])
            print("需要升级的包如下: ", " ".join(packlist))

        # -----------------------------------------
        print("--------------------> 漏洞处理结果如下 <--------------------")
        print("处理CVE漏洞总计： {}个".format(len(all_cve_reports)))
        # ---- 搜索状态异常cve信息输出
        _cvelist_cvenotfound_output(all_cve_reports)
        _cvelist_solutionnotfound_output(all_cve_reports)
        _cvelist_needupdate_package_output(all_cve_reports)

    # 用于向csv文件记录多个漏洞结果
    @classmethod
    def _record_cvereports2csv(cls, all_cve_reports) -> None:
        with open(cls._CveResult_Csvfile, 'w', encoding='UTF-8') as f:
            writer = csv.writer(f, dialect='unix')
            for cve_report in all_cve_reports:
                # 输出格式  漏洞编号、漏洞详情、漏洞影响情况、漏洞解决方案、产品编号、架构
                line = [cve_report["cve_number"],
                        cve_report["detail"],
                        INFLUENCE_INFO.get(str(cve_report["influence"]), "返回值异常"),
                        ["{}: {}: {}".format(i["packname"],i["status"], i["version"]) for i in cve_report["solution"]],
                        product_id[cve_report["productid"]],
                        cve_report["framework"]]
                writer.writerow(line)

    # 用于向日志文件记录多个漏洞信息
    @classmethod
    def _record_cvereports2log(cls, all_cve_reports) -> None:
        for cve_report in all_cve_reports:
            log_format_info =(  "--------> 处理漏洞：{} <--------\n"
                                "  时间： {}\n"
                                "  CVE编号： {}\n"
                                "  产品id: {} ---- {}\n"
                                "  CPU架构： {}\n"
                                "  内网CVE链接： {}\n"
                                "  状态: {}({})\n"
                                "  解决方案: {}".format(
                                                        cve_report["cve_number"],
                                                        time.strftime("%Y-%m-%d %H:%M:%S"),
                                                        cve_report["cve_number"],
                                                        cve_report["productid"], product_id[cve_report["productid"]],
                                                        cve_report["framework"],
                                                        "https://server.kylinos.cn:18089/cve/detail?allTitle=" + cve_report["cve_number"],
                                                        cve_report["influence"], INFLUENCE_INFO.get(str(cve_report["influence"]), "返回值异常"),
                                                        ["{}: {}: {}".format(i["packname"],i["status"], i["version"]) for i in cve_report["solution"]])
                            )
            # -------- 写入日志 --------
            logger = logging.getLogger("自动化漏洞搜索工具")
            logging.basicConfig(filename=LogFile, level=logging.INFO, encoding="UTF-8")
            logger.info(log_format_info)

    @classmethod
    # 用于写入json文件
    def _record_cvereports2json(cls, all_cve_reports):
        all_cve_json_data = []
        for cve_report in all_cve_reports:
            data = {
                "cve_number": cve_report["cve_number"],
                "productid": str(cve_report["productid"]),
                "productname": cls._Productid[cve_report["productid"]],
                "framework": cve_report["framework"],
                "solution": cve_report["solution"]
            }
            all_cve_json_data.append(data)

        # 写入json文件
        with open(cls._CveResult_JsonFile, 'w', encoding="UTF-8") as f:
            json.dump(all_cve_json_data, f,
                      indent=4,
                      ensure_ascii=False,
                      sort_keys=True)






    def processor(self) -> None:
        all_cve_reports = self._get_all_cve_reports()
        self._processor_summary(all_cve_reports)
        # ---- 数据写入 ----
        # 写csv文件
        self._record_cvereports2csv(all_cve_reports)
        # 写日志文件
        self._record_cvereports2log(all_cve_reports)
        # 写json文件
        self._record_cvereports2json(all_cve_reports)















